﻿using System;
using System.Collections.Generic;
using ControleRepresentante.Logic.Models;
using ControleRepresentante.Logic.Statistics;
using NHibernate;
using NHibernate.Criterion;

namespace ControleRepresentante.Logic.Repositories
{
    public abstract class RepositoryBase : IRepository
    {
        private readonly IRepositoryConfiguration m_RepositoryConfiguration;
        private Representante m_Representante;
        private User m_User;

        protected RepositoryBase(IRepositoryConfiguration repositoryConfiguration, ISession session)
        {
            m_RepositoryConfiguration = repositoryConfiguration;
            Session = session;
        }

        protected ISession Session { get; private set; }

        public Representante GetRepresentante()
        {
            if (m_Representante == null && User != null)
            {
                if (User.Ativo)
                {
                    Guid? id = m_RepositoryConfiguration.GetRepresentanteId();
                    if (id.HasValue)
                    {
                        m_Representante = Session.Get<Representante>(id.Value);
                        if (!m_Representante.Ativo)
                        {
                            m_Representante = null;
                        }
                    }
                }
            }
            return m_Representante;
        }

        protected Guid? GetRepresentanteId()
        {
            return m_RepositoryConfiguration.GetRepresentanteId();
        }

        protected void EnsureIsAdmin()
        {
            if (User == null || !User.IsAdmin || !User.Ativo) throw new RestrictedAreaException();
        }

        protected void EnsureIsRepresentante()
        {
            if (m_RepositoryConfiguration.GetRepresentanteId() == null) throw new RestrictedAreaException();
        }

        public User User
        {
            get
            {
                if (m_User == null)
                {
                    UserInfo userInfo = m_RepositoryConfiguration.GetUserInfo();
                    if (userInfo != null && userInfo.Ativo)
                    {
                        m_User = Session.Get<User>(userInfo.Id);
                    }
                }
                return m_User;
            }
        }

        protected static int GetCount(ICriteria criteria)
        {
            criteria = (ICriteria) criteria.Clone();
            return Convert.ToInt32(criteria.SetProjection(Projections.RowCountInt64()).UniqueResult());
        }
    }

    public abstract class RepositoryBase<TModel, TStatistics, TError> : RepositoryBase, IRepository<TModel, TStatistics>
        where TModel : ModelBase 
        where TStatistics : IStatistics
    {
        protected RepositoryBase(IRepositoryConfiguration repositoryConfiguration, ISession session)
            : base(repositoryConfiguration, session)
        {
        }

        protected void ThrowRepositoryException(TError error)
        {
            throw new RepositoryException<TError>(error);
        }

        protected void ThrowRepositoryException(TError error, Exception innerException)
        {
            throw new RepositoryException<TError>(error, innerException);
        }

        public abstract IEnumerable<TModel> GetModels(out TStatistics statistics, int page = -1);
        public abstract IEnumerable<TModel> GetModelsByIds(IEnumerable<Guid> ids);
        public abstract void AddModel(TModel model);
        public abstract TModel GetModelById(Guid id);
        public abstract void UpdateModel(TModel model);
        public abstract void RemoveModelById(Guid id);

        public virtual int PageSize
        {
            get { return 50; }
        }

        protected IEnumerable<TModel> FuturePaging(IQueryOver<TModel, TModel> queryOver, int page, out int totalCount)
        {
            totalCount = queryOver.RowCount();
            if (page > 0)
            {
                return queryOver
                    .Take(PageSize)
                    .Skip((page - 1)*PageSize)
                    .Future();
            }
            return queryOver.Future();
        }

        protected IEnumerable<Object[]> ListPaging(ICriteria criteria, int page, out int totalCount, Func<ICriteria, ICriteria> afterCount = null)
        {
            return Paging(criteria, page, out totalCount, afterCount, x => x.List<Object[]>());
        }

        protected IEnumerable<TModel> FuturePaging(ICriteria criteria, int page, out int totalCount, Func<ICriteria, ICriteria> afterCount = null)
        {
            return Paging(criteria, page, out totalCount, afterCount, x => x.Future<TModel>());
        }

        private IEnumerable<T> Paging<T>(ICriteria criteria, int page, out int totalCount, Func<ICriteria, ICriteria> afterCount, Func<ICriteria, IEnumerable<T>> getResult)
        {
            totalCount =
                Convert.ToInt32(((ICriteria) criteria.Clone()).SetProjection(Projections.RowCountInt64()).UniqueResult());

            if (afterCount != null)
            {
                criteria = afterCount(criteria);
            }

            if (page > 0)
            {
                return getResult(criteria
                        .SetMaxResults(PageSize)
                        .SetFirstResult((page - 1)*PageSize));
            }
            return getResult(criteria);
        }
    }

    public class RepositoryException<T> : Exception
    {
        public T Error { get; private set; }

        public RepositoryException(T error)
        {
            Error = error;
        }

        public RepositoryException(T error, Exception innerException)
            : base(innerException.Message, innerException)
        {
            Error = error;
        }
    }

    public class RestrictedAreaException : Exception
    {
    }
}